/*
 * Public Credit Information Sharing Platform(PCISP).
 * Copyright (c) 2019-2020 jsxzkingdee and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * Please visit https://www.zhengxinsoft.com if you need additional information or have any questions.
 */

package com.platform.comm.jwt;


import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.IoUtil;
import com.google.common.collect.Maps;
import com.platform.comm.config.properties.JwtProperties;
import com.platform.comm.util.CommUtil;
import com.platform.comm.util.DateTimeUtil;
import io.jsonwebtoken.*;
import io.jsonwebtoken.jackson.io.JacksonDeserializer;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.io.Serializable;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * @author coder
 */
@Slf4j
@Component
public class JwtTokenUtil implements Serializable {

    private static final long serialVersionUID = -3301605591108950415L;
    public static final String CLAIM_KEY_USER = "user";
    private final JwtProperties jwtProperties;
    private RSAPublicKey key;

    @Autowired
    public JwtTokenUtil(JwtProperties jwtProperties) {
        this.jwtProperties = jwtProperties;
    }

    @PostConstruct
    public void init() throws Exception {
        Resource resource = new ClassPathResource(jwtProperties.getPubKeyFilePath());
        InputStream is = resource.getInputStream();
        String publicKeyData = IoUtil.read(is).toString();
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec((Base64.decode(publicKeyData)));
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        this.key = (RSAPublicKey) keyFactory.generatePublic(keySpec);
    }

    public JwtUser getUserFromToken(String token) {
        JwtUser jwtUser;
        try {
            final Claims claims = getClaimsFromToken(token);
            jwtUser = claims.get(CLAIM_KEY_USER, JwtUser.class);
        } catch (Exception e) {
            log.error("Get User Detail failed: {}.", e.getMessage());
            jwtUser = null;
        }
        return jwtUser;
    }

    public String generateAccessToken(JwtUser jwtUser) {
        Map<String, Object> claims = Maps.newHashMap();
        claims.put(CLAIM_KEY_USER, jwtUser);
        return generateToken(jwtUser.getUsername(), claims);
    }

    private String generateToken(String subject, Map<String, Object> claims) {
        // 添加自定义参数
        JwtBuilder jwtBuilder = Jwts.builder()
                .setClaims(claims);
        jwtBuilder.setHeaderParam("typ", "JWT");
        jwtBuilder.compressWith(CompressionCodecs.GZIP);
        jwtBuilder.setExpiration(generateExpirationDate());
        jwtBuilder.setSubject(subject);
        jwtBuilder.setAudience(jwtProperties.getAudience());
        jwtBuilder.setIssuer(jwtProperties.getIssuer());
        jwtBuilder.setId(CommUtil.uuid());
        jwtBuilder.setIssuedAt(new Date());
        jwtBuilder.signWith(this.key);
        return jwtBuilder.compact();
    }

    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + jwtProperties.getExpiration() * 1000);
    }

    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            log.error("Get Username failed: {}.", e.getMessage());
            username = null;
        }
        return username;
    }

    private Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = claims.getIssuedAt();
        } catch (Exception e) {
            log.error("Get token create date failure.");
            created = null;
        }
        return created;
    }

    private Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            log.error("Get token Expire failed: {}.", e.getMessage());
            expiration = null;
        }
        return expiration;
    }

    public Claims getClaimsFromToken(String token) {
        Jws<Claims> jws = Jwts.parserBuilder()
                // 解析 JWT 的服务器与创建 JWT 的服务器的时钟不一定完全同步，此设置允许两台服务器最多有 3 分钟的时差
                .setAllowedClockSkewSeconds(180L)
                //.requireAudience(jwtProperties.getAudience())
                //.requireIssuer(jwtProperties.getIssuer())
                .setSigningKey(this.key)
                // 默认情况下 JJWT 只能解析 String, Date, Long, Integer, Short and Byte 类型，如果需要解析其他类型则需要配置 JacksonDeserializer
                .deserializeJsonWith(new JacksonDeserializer(io.jsonwebtoken.lang.Maps.of(CLAIM_KEY_USER, JwtUser.class).build()))
                .build().parseClaimsJws(token);

        return jws.getBody();
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        if (Objects.isNull(expiration)) {
            return true;
        }
        return expiration.before(new Date());
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return Objects.nonNull(lastPasswordReset) && Objects.nonNull(created) && created.before(lastPasswordReset);
    }

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && !isTokenExpired(token);
    }

    public Boolean validateToken(String token, JwtUser jwtUser) {
        return !isTokenExpired(token)
                && !isCreatedBeforeLastPasswordReset(getCreatedDateFromToken(token),
                DateTimeUtil.parseStdLongTime(jwtUser.getPwdExpire()));
    }

    @Data
    public static class JwtUser implements Serializable {
        private String id;
        private String pwdExpire;
        private String departId;
        private String departName;
        private String departKind;
        private String realName;
        private boolean isAdmin;
        private boolean isFirst;
        private String username;
        private String auth;
        private boolean domain;

        public boolean isDomain() {
            return "DOMAIN".equals(getDepartKind());
        }

        public void setDomain(boolean domain) {
            this.domain = domain;
        }
    }
}
